#include "SearchController.h"
#include "SearchControllerThread.h"

#include <QDir>
#include <QDebug>
#include <stack>

namespace {
    /** Work threads count */
    const int g_threadsCount = 4;

    /** Folders will be recursivelly scanned */
    const std::size_t g_folderScanerChuckSize = 1000;

    const int g_atomicTrue = 1;
    const int g_atomicFalse = 0;
}

SearchController::SearchController(QObject *parent, IConfig *config)
    : QThread(parent)
    , m_searchType(ESearchTypeByteArray)
    , m_stopRequested(0)
    , m_wholeProgressDefined(g_atomicFalse)
    , m_totalScanObjects(0)
    , m_completedObjectsCount(0)
    , m_activeThreads(0)
    , m_config(config)
{

}

SearchController::~SearchController()
{
    interrupt();
}

void SearchController::scanFolder(const QString & folder)
{
    if (QThread::isRunning())
        return;

    m_searchType = ESearchTypeFolder;
    m_searchPath = folder;

    QDir dir(folder, QString(), QDir::DirsLast, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks | QDir::Hidden);

    FileScanStructPtr fsStruct(new FileScanStruct(dir.entryInfoList()));
    m_folderScanerStack.push(fsStruct);

    m_wholeProgressDefined = g_atomicFalse;
    m_totalScanObjects = 0;

    initThreads(g_threadsCount);

    QThread::start();
}

void SearchController::scanFile(const QString & filename)
{
    if (QThread::isRunning())
        return;

    m_searchType = ESearchTypeFile;

    QByteArray bytes(filename.toAscii());
    m_taskQueue.push(SearchTaskPtr(new SearchTask(std::string(bytes.data()))));

    m_wholeProgressDefined = g_atomicTrue;
    m_totalScanObjects = 1;

    // There is only one task for execution no needs to start several threads
    initThreads(1);

    QThread::start();
}

void SearchController::scanByteArray(QByteArray & byteArray)
{
    if (QThread::isRunning())
        return;

    m_searchType = ESearchTypeByteArray;

    SearchTask::ByteArray bytes(byteArray.begin(), byteArray.end());
    m_taskQueue.push(SearchTaskPtr(new SearchTask(bytes)));

    m_wholeProgressDefined = g_atomicTrue;
    m_totalScanObjects = 1;

    // There is only one task for execution no needs to start several threads
    initThreads(1);

    QThread::start();
}

void SearchController::initThreads(int count)
{
    for (int i = 0; i < count; ++i)
    {
        SearchControllerThread *thread = new SearchControllerThread(this, m_config, this);
        thread->start();
        m_workThreads.push_back(thread);
    }
}

void SearchController::interrupt()
{
    m_stopRequested = 1;
    m_Event.wakeOne();

    QThread::wait();
}

void SearchController::run()
{
    SearchTaskPtr task;

    while (!m_stopRequested)
    {
        if (NULL == task.get())
            task = getNextTask();

        // if there is no any tasks stop processing
        if (NULL == task.get() && 0 == m_activeThreads)
        {
            m_mutex.unlock();
            break;
        }

        // run tasks processing
        ControllerThreadVector::iterator endThread = m_workThreads.end();
        for (ControllerThreadVector::iterator thread = m_workThreads.begin();
             thread != endThread && !m_stopRequested && NULL != task.get(); ++thread)
        {

            if (!(*thread)->pushTask(task))
            {
                continue;
            }
            m_activeThreads.ref();



            task = getNextTask();
        }

        if (m_stopRequested)
            break;

        m_mutex.lock();
        m_Event.wait(&m_mutex, 100);
        m_mutex.unlock();
    }

    // interrupt all threads
    std::for_each(m_workThreads.begin(), m_workThreads.end(),
                  std::mem_fun(&SearchControllerThread::interrup));

}


int SearchController::recursiveScan(FolderScanerStack & stack, int limit)
{
    int count = 0;

    while (true)
    {
        if (stack.empty() || count >= limit)
            return count;

        FileScanStructPtr currentDir(stack.top());
        QFileInfoList::Iterator end = currentDir->m_infoList.end();

        if (currentDir->m_iter == end)
        {
            stack.pop();
            continue;
        }

        for (; currentDir->m_iter != end; ++currentDir->m_iter)
        {
            const QString & absPath = currentDir->m_iter->absoluteFilePath();

            if (!currentDir->m_iter->isDir())
            {
                m_taskQueue.push(SearchTaskPtr(new SearchTask(absPath.toAscii().data())));
                ++count;
                if (count >= limit)
                {
                    ++currentDir->m_iter;
                    break;
                }

                continue;
            }

            ++currentDir->m_iter;

            QDir dir(absPath, QString(),
                     QDir::DirsLast,
                     QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks | QDir::Hidden);

            FileScanStructPtr fsStruct(new FileScanStruct(dir.entryInfoList()));

            stack.push(fsStruct);

            break;
        }
    }

    return count;
}

SearchTaskPtr SearchController::getNextTask()
{
    if (m_stopRequested)
        return SearchTaskPtr();

    if (!m_taskQueue.empty())
    {
        SearchTaskPtr ret = m_taskQueue.front();
        m_taskQueue.pop();

        return ret;
    }

    if (m_searchType != ESearchTypeFolder)
        return SearchTaskPtr();

    // if it is folder scan search
    // search next scan targets
    const int objCount = recursiveScan(m_folderScanerStack, g_folderScanerChuckSize);
    m_totalScanObjects += objCount;
    if (objCount == 0)
        m_wholeProgressDefined = g_atomicTrue;

    if (!m_taskQueue.empty())
    {
        SearchTaskPtr ret = m_taskQueue.front();
        m_taskQueue.pop();

        return ret;
    }

    return SearchTaskPtr();
}

void SearchController::onProgress(SearchTaskPtr /* task */, SearchControllerThread * /* thread */, float progressValue)
{
    if (ESearchTypeFolder != m_searchType)
    {
        emit sigProgress(progressValue);
        return;
    }
}

void SearchController::guidFound(SearchTaskPtr task, SearchControllerThread *thread,
                       const std::string & guid)
{
    m_activeThreads.deref();

    signalIncProgress();
    emit sigFoundGUID(QString(task->getFilename().c_str()), QString(guid.c_str()));

    m_Event.wakeAll();
}

void SearchController::notFound(SearchTaskPtr task, SearchControllerThread *thread)
{
    m_activeThreads.deref();

    signalIncProgress();

    m_Event.wakeAll();
}

void SearchController::onError(SearchTaskPtr task, SearchControllerThread *thread,
                     const std::string & errMsg)
{
    m_activeThreads.deref();

    signalIncProgress();
    emit sigError(QString(task->getFilename().c_str()), QString(errMsg.c_str()));

    m_Event.wakeAll();
}

void SearchController::onInterrupted(SearchTaskPtr /* task */, SearchControllerThread *thread)
{
    m_activeThreads.deref();

    signalIncProgress();
    emit sigInterrupted();

    m_Event.wakeAll();
}


void SearchController::signalIncProgress()
{
    m_completedObjectsCount.ref(); // +1

    if (ESearchTypeFolder != m_searchType || m_wholeProgressDefined != g_atomicTrue)
        return;

    const float progressValue = (float)m_completedObjectsCount / m_totalScanObjects;
    emit sigProgress(progressValue);
}

